package lua

//TODO
//1. TK_DOTS
import (
	"fmt"
)

type Parser struct {
	t, la Token //curent and lookahead token
	lexer *Lexer
}

func NewParser(l *Lexer) *Parser {
	p := &Parser{lexer: l}
	//init tokens
	p.la.T, p.la.Seminfo = TK_EOS, nil
	p.next()
	return p
}

func (p *Parser) parseError(msg string) {
	panic(fmt.Sprintf("[parse-error]%d:%s", p.lexer.line, msg)) //TODO
}

func (p *Parser) next() {
	if p.la.T != TK_EOS {
		p.t = p.la
		p.la.T, p.la.Seminfo = TK_EOS, nil
	} else {
		p.t.T = p.lexer.NextTok(&p.t.Seminfo)
	}
}

//LL(2)
func (p *Parser) lookahead() {
	if p.la.T == TK_EOS {
		p.la.T = p.lexer.NextTok(&p.la.Seminfo)
	}
}

func (p *Parser) testNext(t int) bool {
	if p.t.T == t {
		p.next()
		return true
	} else {
		return false
	}
}

func (p *Parser) checkNext(t int) (seminfo interface{}) {
	if p.t.T == t {
		seminfo = p.t.Seminfo
		p.next()
	} else {
		p.parseError(fmt.Sprintf("<%s> expected", txtToken(t)))
	}
	return
}

func (p *Parser) blocksEnd() bool {
	switch p.t.T {
	case TK_EOS, tk_end, tk_else, tk_elseif, tk_until:
		return true
	default:
		return false
	}
}

func (p *Parser) Statlist() (ret *StatList) {
	ret = new(StatList)
	for !p.blocksEnd() {
		if p.t.T == tk_return {
			ret.stats = append(ret.stats, p.Statement())
			return
		}
		ret.stats = append(ret.stats, p.Statement())
	}
	return
}

func (p *Parser) Statement() Statement {
	switch p.t.T {
	case ';':
		p.next() //skip empty statement
	case tk_if:
		p.next() //skip if
		return p.IfStat()
	case tk_while:
		p.next() //skip while
		return p.WhileStat()
	case tk_do:
		p.next() //skip do
		return p.DoStat()
	case tk_for:
		p.next() //skip for
		return p.ForStat()
	case tk_repeat:
		p.next()
		return p.RepeatStat()
	case tk_function:
		p.next()
		return p.FunctionStat(false)
	case tk_local:
		p.next()
		if p.testNext(tk_function) { //local function
			return p.FunctionStat(true)
		} else {
			return p.LocalStat()
		}
	case tk_dbcolon:
		p.next()
		return p.LabelStat()
	case tk_return:
		p.next()
		return p.ReturnStat()
	case tk_goto, tk_break:
		return p.GotoStat()
	default: //exprstat -> funccall | assignment
		exp := p.suffixedExp()
		if p.t.T == ',' || p.t.T == '=' { //assignment
			assign := &AssignStat{}
			assign.dsts = append(assign.dsts, exp)
			for {
				if p.testNext(',') {
					assign.dsts = append(assign.dsts, p.suffixedExp())
				} else {
					break
				}
			}
			p.checkNext('=')
			for {
				assign.exps = append(assign.exps, p.Expr())
				if !p.testNext(',') {
					break
				}
			}
			return assign
		} else {
			switch exp.(type) { //funccall
			case *CallExp, *MethodCallExp:
				return exp //TODO: just return a exp?
			default:
				p.parseError("syntax error")
			}
		}
	}
	return nil
}

func (p *Parser) IfStat() *IfStat {
	/* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END */
	ret := new(IfStat)
	ret.ifcond = p.Expr()      //condition
	p.checkNext(tk_then)       //then
	ret.ifblock = p.Statlist() //block
	for p.testNext(tk_elseif) {
		ret.elseifconds = append(ret.elseifconds, p.Expr())
		p.checkNext(tk_then)
		ret.elseifblocks = append(ret.elseifblocks, p.Statlist())
	}
	if p.testNext(tk_else) {
		ret.elseblock = p.Statlist()
	}
	p.checkNext(tk_end)
	return ret
}

func (p *Parser) WhileStat() *WhileStat {
	/* whilestat -> WHILE cond DO block END */
	ret := new(WhileStat)
	ret.cond = p.Expr()

	p.checkNext(tk_do)
	ret.block = p.Statlist()
	p.checkNext(tk_end)
	return ret
}

func (p *Parser) DoStat() *DoStat {
	/* block -> statlist */
	ret := new(DoStat)
	ret.block = p.Statlist()
	p.checkNext(tk_end)
	return ret
}

func (p *Parser) Fornum() *Fornum {
	ret := new(Fornum)
	ret.varname = p.checkNext(tk_name).(string)
	p.checkNext('=')
	ret.start = p.Expr()
	p.checkNext(',')
	ret.limit = p.Expr()
	if p.testNext(',') {
		ret.step = p.Expr()
	}
	p.checkNext(tk_do)
	ret.body = p.Statlist()
	p.checkNext(tk_end)
	return ret
}

func (p *Parser) Forlist() *Forlist {
	ret := new(Forlist)
	for {
		varname := p.checkNext(tk_name).(string)
		ret.vars = append(ret.vars, varname)
		if !p.testNext(',') {
			break
		}
	}
	p.checkNext(tk_in)
	ret.exps = p.ExprList()
	p.checkNext(tk_do)
	ret.body = p.Statlist()
	p.checkNext(tk_end)
	return ret
}

func (p *Parser) ForStat() interface{} {
	/* forstat -> FOR (fornum | forlist) END */
	p.lookahead()
	if p.la.T == '=' {
		return p.Fornum()
	} else if p.la.T == ',' || p.la.T == tk_in {
		return p.Forlist()
	} else {
		p.parseError("only support fornum | forlist")
	}
	return nil
}

func (p *Parser) RepeatStat() *RepeatStat {
	/* repeatstat -> REPEAT block UNTIL cond */
	ret := new(RepeatStat)
	ret.block = p.Statlist()
	p.checkNext(tk_until)
	ret.cond = p.Expr()
	return ret
}

func (p *Parser) FunctionStat(local bool) *FunctionStat {
	/* funcstat -> FUNCTION funcname body */
	name := []string{}
	name = append(name, p.checkNext(tk_name).(string))
	if !local {
		for p.testNext('.') {
			name = append(name, p.checkNext(tk_name).(string))
		}
		if p.testNext(':') {
			name = append(name, p.checkNext(tk_name).(string))
		}
	}
	body := p.funcBody()
	return &FunctionStat{local, name, body}
}

func (p *Parser) LocalStat() *LocalStat {
	/* stat -> LOCAL NAME {',' NAME} ['=' explist] */
	//如果在一个确定的parse分支里面就应该是去掉第一个token之后的token stream，所以这里默认的是'local'已经被脱掉
	//假如这个分支内部还有分支，那这个token不应被脱掉
	var exps *ExprList
	names := []string{}
	for {
		name := p.checkNext(tk_name)
		names = append(names, name.(string))
		if !p.testNext(',') {
			break
		}
	}
	if p.testNext('=') {
		exps = p.ExprList()
	}
	return &LocalStat{varnames: names, exps: exps}
}

func (p *Parser) LabelStat() *LabelStat {
	/* label -> '::' NAME '::' */
	name := p.checkNext(tk_name).(string)
	p.checkNext(tk_dbcolon)
	return &LabelStat{name: name}
}

func (p *Parser) ReturnStat() *ReturnStat {
	/* stat -> RETURN [explist] [';'] */
	var exps *ExprList
	if !p.blocksEnd() && p.t.T != ';' {
		exps = p.ExprList()
	}
	p.testNext(';')
	return &ReturnStat{exps: exps}
}

func (p *Parser) GotoStat() *GotoStat {
	/* stat -> 'goto' NAME */
	ret := new(GotoStat)
	if p.testNext(tk_goto) {
		ret.label = p.checkNext(tk_name).(string)
	} else {
		p.checkNext(tk_break)
	}
	return ret
}

func (p *Parser) ExprList() *ExprList {
	ret := new(ExprList)
	for {
		ret.exps = append(ret.exps, p.Expr())
		if !p.testNext(',') {
			break
		}
	}
	return ret
}

func (p *Parser) Expr() Expr {
	return p.subExpr(0)
}

//binary operators
const (
	OPR_ADD = iota
	OPR_SUB
	OPR_MUL
	OPR_MOD
	OPR_POW
	OPR_DIV
	OPR_IDIV
	OPR_BAND
	OPR_BOR
	OPR_BXOR
	OPR_SHL
	OPR_SHR
	OPR_CONCAT
	OPR_EQ
	OPR_LT
	OPR_LE
	OPR_NE
	OPR_GT
	OPR_GE
	OPR_AND
	OPR_OR
	OPR_NOBINOPR
)

//unary operators
const (
	OPR_MINUS = iota
	OPR_BNOT
	OPR_NOT
	OPR_LEN
	OPR_NOUNOPR
)

func getBinOpr(tk int) int {
	switch tk {
	case '+':
		return OPR_ADD
	case '-':
		return OPR_SUB
	case '*':
		return OPR_MUL
	case '%':
		return OPR_MOD
	case '^':
		return OPR_POW
	case '/':
		return OPR_DIV
	case tk_idiv:
		return OPR_IDIV
	case '&':
		return OPR_BAND
	case '|':
		return OPR_BOR
	case '~':
		return OPR_BXOR
	case tk_shl:
		return OPR_SHL
	case tk_shr:
		return OPR_SHR
	case tk_concat:
		return OPR_CONCAT
	case tk_eq:
		return OPR_EQ
	case '<':
		return OPR_LT
	case tk_le:
		return OPR_LE
	case tk_ne:
		return OPR_NE
	case '>':
		return OPR_GT
	case tk_ge:
		return OPR_GE
	case tk_and:
		return OPR_AND
	case tk_or:
		return OPR_OR
	default:
		return OPR_NOBINOPR
	}
}

func getUnopr(tk int) int {
	switch tk {
	case '-':
		return OPR_MINUS
	case '~':
		return OPR_BNOT
	case tk_not:
		return OPR_NOT
	case '#':
		return OPR_LEN
	default:
		return OPR_NOUNOPR
	}
}

//left priority(when operand on the left), right priority(when operand on the right)
var priority = [...][2]int{
	{10, 10}, {10, 10}, /* '+' '-' */
	{11, 11}, {11, 11}, /* '*' '%' */
	{14, 13},           /* '^' (right associative) */
	{11, 11}, {11, 11}, /* '/' '//' */
	{6, 6}, {4, 4}, {5, 5}, /* '&' '|' '~' */
	{7, 7}, {7, 7}, /* '<<' '>>' */
	{9, 8},                 /* '..' (right associative) */
	{3, 3}, {3, 3}, {3, 3}, /* ==, <, <= */
	{3, 3}, {3, 3}, {3, 3}, /* ~=, >, >= */
	{2, 2}, {1, 1}} /* and, or */

const UNARY_PRIORITY = 12

func (p *Parser) subExpr(limit int) (ret Expr) {
	/*
	** subexpr -> (simpleexp | unop subexpr) { binop subexpr }
	** where 'binop' is any binary operator with a priority higher than 'limit'
	 */
	unop := getUnopr(p.t.T)
	if unop != OPR_NOUNOPR {
		p.next()
		ret = &UnaryExpr{op: unop, exp: p.subExpr(UNARY_PRIORITY)}
	} else {
		ret = p.simpleExp()
	}
	for {
		binop := getBinOpr(p.t.T)
		if binop != OPR_NOBINOPR && priority[binop][0] > limit {
			p.next()
			ret = &BinExpr{op: binop, left: ret, right: p.subExpr(priority[binop][1])}
		} else {
			break
		}
	}
	return
}

func (p *Parser) simpleExp() (ret Expr) {
	/* simpleexp -> FLT | INT | STRING | NIL | TRUE | FALSE | ... |
	   constructor | FUNCTION body | suffixedexp */
	switch p.t.T {
	case tk_string:
		ret = &StrLiteral{value: p.t.Seminfo.(string)}
		p.next()
	case '{':
		ret = p.constructor()
	case tk_function:
		ret = p.funcBody()
	case tk_flt:
		ret = &FloatLiteral{value: p.t.Seminfo.(float64)}
		p.next()
	case tk_int:
		ret = &IntLiteral{value: p.t.Seminfo.(int)}
		p.next()
	case tk_nil:
		ret = &NilLiteral{}
		p.next()
	case tk_true:
		ret = &BooleanLiteral{value: true}
		p.next()
	case tk_false:
		ret = &BooleanLiteral{value: false}
		p.next()
	case tk_dots:
		ret = &DotsLiteral{}
		p.next()
	default:
		ret = p.suffixedExp()
	}
	//	p.next()
	return
}

func (p *Parser) suffixedExp() (ret Expr) {
	/* suffixedexp ->
	   primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */
	ret = p.primaryExp()
	for {
		switch p.t.T {
		case '.': //NAME
			p.next()
			ret = &AttrExp{
				attribute: p.checkNext(tk_name).(string),
				base:      ret}
		case '[': //[exp]
			p.next()
			ret = &IndexExp{
				base:    ret,
				indexer: p.Expr()}
			p.checkNext(']')
		case ':': //:NAME funcargs
			var base Expr = ret
			p.next()
			attr := p.checkNext(tk_name).(string)
			args := p.funcArgs()
			ret = &MethodCallExp{base: base, attr: attr, args: args}
		case '(', '{', tk_string: //funcargs
			ret = &CallExp{base: ret, args: p.funcArgs()}
		default:
			return
		}
	}
	return
}

func (p *Parser) primaryExp() (ret Expr) {
	/* primaryexp -> NAME | '(' expr ')' */
	switch p.t.T {
	case tk_name:
		ret = &NameExpr{identifier: p.t.Seminfo.(string)}
		p.next()
	case '(':
		p.next()
		ret = p.Expr()
		p.checkNext(')')
	default:
		p.parseError("unexpected symbol")
	}
	return
}

func (p *Parser) funcArgs() (ret Expr) {
	switch p.t.T {
	case '(': /* funcargs -> '(' [ explist ] ')' */
		p.next()
		if !p.testNext(')') { //not empty
			ret = p.ExprList()
			p.checkNext(')')
		}
	case '{': /* funcargs -> constructor */
		ret = p.constructor()
	case tk_string: /* funcargs -> STRING */
		ret = &StrLiteral{value: p.t.Seminfo.(string)}
		p.next()
	default:
		p.parseError("function args expected")
	}
	return
}

func (p *Parser) constructor() (ret Expr) {
	/* constructor -> '{' [ field { sep field } [sep] ] '}'
	   sep -> ',' | ';' */
	exp := new(Constructor)
	p.next()              //skip '{'
	if !p.testNext('}') { //not empty
		exp.fields = append(exp.fields, p.field())
		for p.t.T == ',' || p.t.T == ';' {
			p.next()
			if p.testNext('}') {
				return exp
			} else {
				exp.fields = append(exp.fields, p.field())
			}
		}
		p.testNext('}')
	}
	return exp
}

func (p *Parser) field() (ret Expr) {
	/* field -> listfield | recfield */
	switch p.t.T {
	case tk_name:
		p.lookahead()
		if p.la.T != '=' {
			ret = p.listfield()
		} else {
			ret = p.recfield()
		}
	case '[':
		ret = p.recfield()
	default:
		ret = p.listfield()
	}
	return
}

func (p *Parser) listfield() (ret Expr) {
	/* listfield -> exp */
	return p.Expr()
}

func (p *Parser) recfield() (ret Expr) {
	/* recfield -> (NAME | '['exp1']') = exp1 */
	exp := new(RecField)
	switch p.t.T {
	case tk_name:
		exp.attr = &StrLiteral{value: p.t.Seminfo.(string)}
		p.next()
		p.checkNext('=')
		exp.value = p.Expr()
		ret = exp
	case '[':
		p.next()
		exp.attr = p.Expr()
		p.checkNext(']')
		p.checkNext('=')
		exp.value = p.Expr()
		ret = exp
	default:
		p.parseError("this can not happen")
	}
	return
}

func (p *Parser) funcBody() (ret *FuncBody) {
	ret = new(FuncBody)
	p.checkNext('(')
	if !p.testNext(')') {
		for {
			varname := p.checkNext(tk_name).(string)
			ret.params = append(ret.params, varname)
			if !p.testNext(',') {
				p.checkNext(')')
				break
			}
		}
	}
	ret.body = p.Statlist()
	p.checkNext(tk_end)
	return
}
